package org.theSeed.web.filter;

import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.util.StreamUtils;
import org.theSeed.base.pojo.constant.SysConstant;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;

import static org.theSeed.web.sysEnum.FilterOrder.REPEAT_READ;

/**
 * 可重复读过滤器
 */
@Slf4j
public class RepeatReadFilter implements Filter, Ordered {
    public RepeatReadFilter() {
    }

    public RepeatReadFilter(Integer orderNum) {
        this.orderNum = orderNum;
    }

    protected Integer orderNum = REPEAT_READ.getCode();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //请求体处理
        HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
        if (CachedBodyHttpServletRequestWrapper.isRequestBodyCompatible(httpServletRequest))
            servletRequest = new CachedBodyHttpServletRequestWrapper(httpServletRequest);

        //响应体处理
        HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;
        // 1. 包装原始响应
        servletResponse = new CachedBodyHttpServletResponseWrapper(httpServletResponse);

        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public int getOrder() {
        return orderNum;
    }


    /**
     * 可重复读取请求体的 HttpServletRequest 包装器
    */
    public static class CachedBodyHttpServletRequestWrapper extends HttpServletRequestWrapper {

        // 用于缓存请求体数据的字节数组
        private final byte[] cachedBody;

        /**
         * 构造方法，在构造时读取并缓存请求体
         */
        public CachedBodyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            // 使用 Spring 的 StreamUtils 将 InputStream 转换为字节数组
            this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
        }

        /**
         * 重写 getInputStream，返回基于缓存字节数组的新流
         */
        @Override
        public ServletInputStream getInputStream() throws IOException {
            // 基于缓存的字节数组创建一个新的 ByteArrayInputStream
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
            return new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return byteArrayInputStream.available() == 0;
                }

                @Override
                public boolean isReady() {
                    return true;
                }

                @Override
                public void setReadListener(ReadListener readListener) {
                    // 不需要实现
                }

                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
            };
        }

        /**
         * 判断请求是否具有可读取的请求体
         * 可以根据实际需求调整条件
         */
        public static boolean isRequestBodyCompatible(HttpServletRequest httpServletRequest) {
            String contentType = httpServletRequest.getContentType();
            if (ObjectUtil.isNotEmpty(contentType)){
                String method = httpServletRequest.getMethod();
                // 通常只有 POST, PUT, PATCH 等方法才有请求体
                // 并且 content-type 不为空（例如排除 multipart/form-data，因为文件上传处理方式不同）
                return (("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)) && (contentType.startsWith("application/json") || contentType.startsWith("application/xml")))
                        || "GET".equalsIgnoreCase(method);
            }else
                return true;
        }

        /**
         * 重写 getReader，返回基于缓存字节数组的 BufferedReader
         */
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(this.getInputStream()));
        }

        /**
         * 提供一个方法，方便后续直接获取缓存体的内容（例如记录日志）
         */
        public String getBody() {
            return new String(cachedBody, SysConstant.defaultCharset);
        }
    }



    /**
     * 可缓存响应体的 HttpServletResponse 包装器
     */
    public static class CachedBodyHttpServletResponseWrapper extends HttpServletResponseWrapper {

        private final ByteArrayOutputStream contentCache = new ByteArrayOutputStream();
        private ServletOutputStream outputStream;
        private PrintWriter writer;
        //是否可重复读
        private Boolean responseBodyCompatible = null;

        public CachedBodyHttpServletResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        public Boolean getResponseBodyCompatible() {
            return responseBodyCompatible;
        }

        /**
         * 重写 getOutputStream，返回我们自定义的、带缓存功能的流
         */
        @Override
        public synchronized ServletOutputStream getOutputStream() throws IOException {
            //
            if (ObjectUtil.isNull(responseBodyCompatible)){
                responseBodyCompatible = isResponseBodyCompatible(this);
                if (responseBodyCompatible) {
                    if (ObjectUtil.isNull(outputStream)){
                        outputStream = new CachedServletOutputStream(getResponse().getOutputStream(), contentCache);
                    }
                }else
                    outputStream = getResponse().getOutputStream();
            }else if (responseBodyCompatible){
                if (ObjectUtil.isNull(outputStream)){
                    outputStream = new CachedServletOutputStream(getResponse().getOutputStream(), contentCache);
                }
            }else{
                outputStream = getResponse().getOutputStream();
            }
            return outputStream;
        }

        /**
         * 重写 getWriter，返回基于我们自定义 OutputStream 的 PrintWriter
         */
        @Override
        public synchronized PrintWriter getWriter() throws IOException {
            if (ObjectUtil.isNull(writer)) {
                // 使用自定义的 OutputStream 来创建 Writer，确保字符编码正确
                writer = new PrintWriter(new OutputStreamWriter(getOutputStream(), getCharacterEncoding()));
            }
            return writer;
        }

        /**
         * 排除某些不需要处理的请求（例如静态资源、文件下载等）
         */
        protected boolean isResponseBodyCompatible(HttpServletResponse httpServletResponse){
            String contentType = httpServletResponse.getContentType();
            if (ObjectUtil.isNotEmpty(contentType)){
                return contentType.startsWith("application/json") || contentType.startsWith("application/xml");
            }else{
                return false;
            }
        }

        /**
         * 提供一个方法，获取缓存中的响应体内容（字节形式）
         */
        public byte[] getContentAsBytes() {
            // 如果使用了Writer，需要确保缓冲区内容被刷新到底层的OutputStream中
            if (writer != null) {
                writer.flush();
            }
            return contentCache.toByteArray();
        }

        /**
         * 提供一个方法，获取缓存中的响应体内容（字符串形式）
         */
        public String getContentAsString() {
            return new String(getContentAsBytes(), StandardCharsets.UTF_8); // 可根据实际编码调整
        }

        /**
         * 自定义的 ServletOutputStream，实现缓存功能
         */
        public class CachedServletOutputStream extends ServletOutputStream {

            private final ServletOutputStream originalStream;
            private final ByteArrayOutputStream cache;

            public CachedServletOutputStream(ServletOutputStream originalStream, ByteArrayOutputStream cache) {
                this.originalStream = originalStream;
                this.cache = cache;
            }

            @Override
            public void write(int b) throws IOException {
                // 同时写入原始流和缓存
                originalStream.write(b);
                cache.write(b);
            }

            @Override
            public void write(byte[] b) throws IOException {
                write(b, 0, b.length);
            }

            @Override
            public void write(byte[] b, int off, int len) throws IOException {
                // 同时写入原始流和缓存
                originalStream.write(b, off, len);
                cache.write(b, off, len);
            }

            @Override
            public void flush() throws IOException {
                originalStream.flush();
                cache.flush();
            }

            @Override
            public void close() throws IOException {
                originalStream.close();
                // 通常不关闭缓存，因为还需要读取它
            }

            @Override
            public boolean isReady() {
                return originalStream.isReady();
            }

            @Override
            public void setWriteListener(WriteListener writeListener) {
                originalStream.setWriteListener(writeListener);
            }
        }
    }
}